iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
自我挑戰組

30 天 vueuse 原始碼閱讀與實作系列 第 19

[Day 19] useDebounceFn - unit test

  • 分享至 

  • xImage
  •  

今天來看 useDebounceFn API 相關的單元測試~

測試架構

// src/utils/filter.test.js

import { ref } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createFilterWrapper, debounceFilter } from '@/utils/filter'

describe('filters', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })
  
  // 測試案例放這邊
  
})

主要是在每個測試案例開始前,mock 案例中會用到的所有 timer。測試案例都會放在註解那個層級位置。

should debounce

// src/utils/filter.test.js

it('should debounce', () => {
    const debouncedFilterSpy = vi.fn()
    const filter = createFilterWrapper(debounceFilter(1000), debouncedFilterSpy)

    setTimeout(filter, 200)
    vi.runAllTimers()

    setTimeout(filter, 500)
    vi.advanceTimersByTime(500)
    expect(debouncedFilterSpy).toHaveBeenCalledOnce()
})

debouncedFilterSpy 是在什麼時候被觸發一次的呢?主要是這行 vi.runAllTimers(),他會執行 setTimeout(filter, 200) 中的 filter,也就是不用真的等 200ms,而 filter 被執行代表 debounceFilter 中的 1000ms timer 也啟動了,這個也一樣因為 vi.runAllTimers() 的關係,不用真的等 1000ms,會直接執行 debouncedFilterSpy。

接下來在測試 setTimeout(filter, 500) ,使用 vi.advanceTimersByTime(500) 讓時間過了 500ms 後,因為還不到我們給 debounceFilter 設定的 1000ms,所以這個不會觸發 debouncedFilterSpy。

should debounce twice

it('should debounce twice', () => {
    const debouncedFilterSpy = vi.fn()
    const filter = createFilterWrapper(debounceFilter(500), debouncedFilterSpy)

    setTimeout(filter, 500)
    vi.advanceTimersByTime(500)
    setTimeout(filter, 1000)
    vi.advanceTimersByTime(2000)

    expect(debouncedFilterSpy).toHaveBeenCalledTimes(2)
})

這個測試滿單純的,測試 timer 時間到的時候,有沒有執行 debouncedFilterSpy,另外 vi.advanceTimersByTime(2000) 這邊的 2000,改成 1500 也會過,因為這個案例 debounceFilter 設定的 ms 是 500。

should resolve & reject debounced fn

it('should resolve & reject debounced fn', async () => {
    const debouncedSum = createFilterWrapper(
      debounceFilter(500, { rejectOnCancel: true }),
      (a, b) => a + b,
    )

    const five = debouncedSum(2, 3)
    let nine
    setTimeout(() => {
      nine = debouncedSum(4, 5)
    }, 200)

    vi.runAllTimers()

    await expect(five).rejects.toBeUndefined()
    await expect(nine).resolves.toBe(9)
})

這個是在測試 rejectOnCancel 為 true 的情境,被取消執行的 Promise 是否有 reject、成功執行的是否有 resolve。這個案例 debounceFilter 設定的 ms 是 500ms,所以在執行vi.runAllTimers() 的時候,five 這個 Promise 會變成 rejected 狀態,nine 則是會變成 resolved。

should debounce with ref

it('should debounce with ref', () => {
    const debouncedFilterSpy = vi.fn()
    const debounceTime = ref(0)
    const filter = createFilterWrapper(debounceFilter(debounceTime), debouncedFilterSpy)

    filter()
    debounceTime.value = 500
    filter() // <- 這個會被取消
    setTimeout(filter, 200)

    vi.runAllTimers()

    expect(debouncedFilterSpy).toHaveBeenCalledTimes(2)
})

測試 debounceTime 是 ref 物件的情境,第一個 filter() 因為 debounceTime.value 是 0,所以這個會直接執行 debouncedFilterSpy。在這之後把 debounceTime.value 改成 500ms,接下來的兩個 filter(),timer 都已經被改成 500ms,所以在 vi.runAllTimers() 執行後,有註解的那個 filter() 會被取消,最後 debouncedFilterSpy 會被執行兩次。也就是說,如果 ref 失效的話,debounceTime.value 會一直都是 0,最後結果會是非預期的三次。

GitHub PR:https://github.com/RhinoLee/30days_vue/pull/18/files


今天透過 unit test 原始碼學習了一些 timer 情境的測試方式,覺得對於 vitest API 的掌握度也滿重要的,文件可能要再看熟一點 XD

useDebounceFn 到今天就告一段落了,明天會開始看 useScroll~


上一篇
[Day 18] useDebounceFn
下一篇
[Day 20] useScroll - X position, Y position
系列文
30 天 vueuse 原始碼閱讀與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言